/**
 * The 'Texture' class defines a standard interface for loading 2D textures into the GL.
 * This class supports progressive loading.
 * The list of supported filenames are available in the enum 'FileFormat'.
 *
 * How to use a texture:
 * 1- create an instance of the Texture class
 * 2- set your texture source, for instance call setFilename(my_texture_filename)
 * 3- EITHER load parts of the texture using load(number_of_bytes_to_load) consecutively
 *    OR load the whole texture using load(-1)
 * 4- call bind() before any GL instruction that needs the texture
 *    (warning: bind() has to be called outside a glBegin/glEnd pair)
 *
 * You do not need to call any particular method like delete() to free the texture memory.
 * Deleting the texture instance will take care of it.
 *
 * During Step 3, please handle the returned value so you can detect disk errors or whatever error.
 *
 * WARNING : ONCE THE bind() METHOD HAS BEEN CALLED, THERE MAY NOT BE ANY CALL TO setFilter, setWrap
 * OR ANY OTHER METHOD THAN bind.
 *
 * And please note the you may be absolutely sure that the GL context is created when you call bind().
 *
 * You can call bind() when the texture has not been [completely] loaded, but results are undefined.
 *
 * If you want to know what is the last generated error, call load(0).
 *
 * If you do not set a filename or a procedural wish, the bind() still works but method loads a default texture.
 * Note that the default texture may also be loaded if either loading was not finished, ie if loading failed
 * or if progressive loading was not complete.
 *
 * As a final note, please remember that load(nb_bytes) only _asks_ for _nb_bytes_, but for particular reasons,
 * loading algorithms may load a different number of bytes (for instance when there are 10 bytes left to read
 * at the end of the file, and the code calls load(16384)). Though, we tend to respect this nb_bytes wish as
 * much as possible.
 */
#ifndef _TEXTURE_H_
#define _TEXTURE_H_

#include "main.h"

#include <cstdio>

class Texture
{
public:
	Texture();
	~Texture();

	void bind();

	enum LoadingState
	{
		// File errors
		ErrorNullFilename,	// File Name does not correspond to a valid name (eg NULL)
		ErrorFileNotFound,	// Unable to open the file : file not found or disk error
		ErrorUnknownFormat,	// Format does not correspond to the list of known file types
		ErrorFileCorrupted,	// File corrupted or format version too recent to be supported

		// Procedural errors
		ErrorInvalidParams,	// Wrong type for parameters or parameters out of range
		ErrorUnknownProc,	// Specified procedural type is not in the list

		// General errors
		ErrorOutOfMemory,	// Not enough memory available to allocate pixels
		ErrorAlreadyBound,	// Because the texture has already been bound, at least once, it is forbidden to load again

		// No error
		InProgress,			// Texture partially loaded
		Finished			// Texture loading complete
	};

	LoadingState load(int nb_bytes = -1);	// Progressive loading. Loads nb_bytes bytes now. If nb_bytes equals -1, then the whole texture is loaded now.

	enum FileFormat
	{
		Auto,		// Automatic detection
		Bitmap,		// BMP
//		Jpeg,		// JPG or JPEG
		Targa,		// TGA
//		Gif,		// GIF
//		Png			// PNG
	};

	void setFilename(const char *filename, FileFormat format = Auto);

	enum ProceduralType
	{
		Noise,
		NoiseRGB,
//		HLines,
//		VLines,
//		HGradient,
//		VGradient,
	};

	void setProcedure(ProceduralType type, unsigned int width, unsigned int height);

	enum TextureFilter
	{
		Nearest,
		Linear
	};

	void setFilter(TextureFilter filter);

	enum TextureWrap
	{
		Clamp,
		Repeat
	};

	void setWrap(TextureWrap wrap);

	enum TextureMipmapping
	{
		NoMipmap,
		MeanMipmap,	// Standard mipmapping
		MaxMipmap,	// Each mipmap level keep the highest texel color amongst precedent level texels
	};

	void setMipmaps(TextureMipmapping mipmapping);

	void setFogMipmap(const float fog_color[4], unsigned int start_level = 1);
	void setFogMipmap(float fog_red, float fog_green, float fog_blue, float fog_alpha, unsigned int start_level = 1);

private:
	/* Raw data */
	unsigned int	w;
	unsigned int	h;
	unsigned char*	pixels;

	/* GL info */
	unsigned int	id;
	unsigned int	server_format;
	unsigned int	client_format;

	bool	bound;

	TextureFilter		filter;
	TextureWrap			wrap;
	TextureMipmapping	mipmap;
	float				fog_color[4];
	unsigned int		fog_start;
	bool				fog_enabled;

	class Loader
	{
	protected:
		Texture* const	texture;
	public:
		Loader(Texture* _texture) : texture(_texture) {}
		virtual~Loader() {}

		virtual LoadingState load(int nb_bytes = -1) = 0;
	};

	class FileLoader : public Loader
	{
	protected:
		const char*	const filename;
	public:
		FileLoader(Texture* _texture, const char* _filename) : Loader(_texture), filename(_filename) {}
		virtual~FileLoader() {}
	};

	class BmpLoader : public FileLoader
	{
	public:
		BmpLoader(Texture* _texture, const char* _filename);
		virtual~BmpLoader();

		virtual LoadingState load(int nb_bytes = -1);

	protected:
		FILE*	file;

#ifdef WIN32
#pragma pack(push, TEXTURE_H)
#pragma pack(1)
#endif
		struct
		{
			/* 0000h */
			unsigned char identifier[2];

			/* 0002h */
			union
			{
				unsigned char _file_size[4];
				unsigned long file_size;
			};

			/* 0006h */
			unsigned char reserved[4];

			/* 000Ah */
			union
			{
				unsigned char _data_offset[4];
				unsigned long data_offset;
			};

			/* 000Eh */
			union
			{
				unsigned char _header_size[4];
				unsigned long header_size;
			};

			/* 0012h */
			union
			{
				unsigned char _width[4];
				unsigned long width;
			};

			/* 0016h */
			union
			{
				unsigned char _height[4];
				unsigned long height;
			};

			/* 001Ah */
			union
			{
				unsigned char _planes[2];
				unsigned short planes;
			};

			/* 001Ch */
			union
			{
				unsigned char _bpp[2];
				unsigned short bpp;
			};

			/* 001Eh */
			union
			{
				unsigned char _compression[4];
				unsigned long compression;
			};

			/* 0022h */
			union
			{
				unsigned char _data_size[4];
				unsigned long data_size;
			};

			/* 0026h */
			union
			{
				unsigned char _hres[4];
				unsigned long hres;
			};

			/* 002Ah */
			union
			{
				unsigned char _vres[4];
				unsigned long vres;
			};

			/* 002Eh */
			union
			{
				unsigned char _colors[4];
				unsigned long colors;
			};

			/* 0032h */
			union
			{
				unsigned char _important[4];
				unsigned long important;
			};
		} BmpHeader;
#ifdef WIN32
#pragma pack(pop, TEXTURE_H)
#endif

		unsigned char* palette;

		unsigned char* data;

		void endian();
	};
	friend class BmpLoader;

	class TgaLoader : public FileLoader
	{
	public:
		TgaLoader(Texture* _texture, const char* _filename);
		virtual~TgaLoader();

		virtual LoadingState load(int nb_bytes = -1);

	protected:
		FILE*	file;

		unsigned char	header[6];

		unsigned char*	data;
		unsigned int	width;
		unsigned int	height;
		unsigned int	bpp;
		unsigned long	size;
	};
	friend class TgaLoader;

	class ProceduralLoader : public Loader
	{
	protected:
		const unsigned int	width;
		const unsigned int	height;
	public:
		ProceduralLoader(Texture* _texture, unsigned int _width, unsigned int _height) : Loader(_texture), width(_width), height(_height) {}
		virtual~ProceduralLoader() {}
	};

	class NoiseLoader : public ProceduralLoader
	{
	public:
		NoiseLoader(Texture* _texture, unsigned int _width, unsigned int _height);
		virtual~NoiseLoader();
		virtual LoadingState load(int nb_bytes = -1);
	};
	friend class NoiseLoader;

	class NoiseRGBLoader : public ProceduralLoader
	{
	public:
		NoiseRGBLoader(Texture* _texture, unsigned int _width, unsigned int _height);
		virtual~NoiseRGBLoader();
		virtual LoadingState load(int nb_bytes = -1);
	};
	friend class NoiseRGBLoader;

	enum LoadingType
	{
		NotSet,
		File,
		Procedural
	};

	LoadingType		loading_type;
	LoadingState	last_error;
	int				cursor;

	FileFormat		format;
	char*			filename;
	ProceduralType	proc_type;
	unsigned int	proc_width;
	unsigned int	proc_height;

	Loader*			loader;

	void StopLoading();
};


#endif
